UsefulTools.ts 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. import { _decorator, Component, Layers, math, Node, Quat, Vec2, Vec3 } from 'cc';
  2. const { ccclass, property } = _decorator;
  3. @ccclass('UsefulTools')
  4. export class UsefulTools {
  5. //是否getter方法
  6. static isGetter(obj: any, prop: string): boolean {
  7. const descriptor =
  8. Object.getOwnPropertyDescriptor(obj, prop) || Object.getOwnPropertyDescriptor(Object.getPrototypeOf(obj), prop);
  9. return !!descriptor?.get;
  10. }
  11. //安全地取属性值(变量/getter方法/普通方法)
  12. static safeGetValue(obj: any, prop: string): any {
  13. const descriptor =
  14. Object.getOwnPropertyDescriptor(obj, prop) || Object.getOwnPropertyDescriptor(Object.getPrototypeOf(obj), prop);
  15. if (descriptor?.get) {
  16. return obj[prop]; // 触发 getter
  17. } else if (typeof obj[prop] === 'function') {
  18. return obj[prop](); // 执行普通方法
  19. } else {
  20. return obj[prop]; // 返回普通属性
  21. }
  22. }
  23. /**
  24. * 判断一个多边形(顶点有序)的顶点数组是否存在自相交
  25. * @param points Vec2[] 多边形顶点(顺序排列,首尾可不闭合)
  26. * @returns result 是否自相交 ,index 自相交的第一个点的索引
  27. * 注意:Vec3的z轴会被忽略
  28. */
  29. public static isPolygonSelfIntersect(points: Vec2[] | Vec3[]): { result: boolean; index: number } {
  30. const n = points.length;
  31. if (n < 4) return { result: false, index: -1 }; // 三角形不可能自相交
  32. // 检查所有非相邻边是否相交(仅检查真正非相邻的边)
  33. for (let i = 0; i < n - 1; i++) {
  34. const a1 = points[i];
  35. const a2 = points[i + 1];
  36. for (let j = i + 2; j < n - 1; j++) {
  37. // 跳过相邻边
  38. if (i === 0 && j === n - 2) continue; // 首尾边不算
  39. const b1 = points[j];
  40. const b2 = points[j + 1];
  41. if (this.segmentsIntersect(a1, a2, b1, b2)) {
  42. return { result: true, index: i };
  43. }
  44. }
  45. }
  46. return { result: false, index: -1 };
  47. }
  48. /**
  49. * 判断两线段是否相交
  50. * 注意:Vec3的z轴会被忽略
  51. */
  52. public static segmentsIntersect(p1: Vec2 | Vec3, p2: Vec2 | Vec3, q1: Vec2 | Vec3, q2: Vec2 | Vec3): boolean {
  53. // 快速排斥
  54. // 快速排斥
  55. if (
  56. Math.max(p1.x, p2.x) < Math.min(q1.x, q2.x) ||
  57. Math.max(q1.x, q2.x) < Math.min(p1.x, p2.x) ||
  58. Math.max(p1.y, p2.y) < Math.min(q1.y, q2.y) ||
  59. Math.max(q1.y, q2.y) < Math.min(p1.y, p2.y)
  60. ) {
  61. return false;
  62. }
  63. const d1 = this.direction(q1, q2, p1);
  64. const d2 = this.direction(q1, q2, p2);
  65. const d3 = this.direction(p1, p2, q1);
  66. const d4 = this.direction(p1, p2, q2);
  67. // Proper intersection
  68. if (d1 * d2 < 0 && d3 * d4 < 0) {
  69. return true;
  70. }
  71. // Special Cases: check for colinear and endpoint overlap
  72. if (d1 === 0 && this.onSegment(q1, q2, p1)) return true;
  73. if (d2 === 0 && this.onSegment(q1, q2, p2)) return true;
  74. if (d3 === 0 && this.onSegment(p1, p2, q1)) return true;
  75. if (d4 === 0 && this.onSegment(p1, p2, q2)) return true;
  76. return false;
  77. }
  78. // 判断点c是否在线段ab上(假设三点共线)
  79. private static onSegment(a: Vec2 | Vec3, b: Vec2 | Vec3, c: Vec2 | Vec3): boolean {
  80. return (
  81. Math.min(a.x, b.x) <= c.x && c.x <= Math.max(a.x, b.x) && Math.min(a.y, b.y) <= c.y && c.y <= Math.max(a.y, b.y)
  82. );
  83. }
  84. // * 计算向量叉积
  85. // * 注意:Vec3的z轴会被忽略
  86. // */
  87. public static direction(a: Vec2 | Vec3, b: Vec2 | Vec3, c: Vec2 | Vec3): number {
  88. return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
  89. }
  90. //从一组Vec2[]或者Vec3[]中判断所有点是否共线
  91. //注意: Vec3的z轴会被忽略
  92. //返回值: { result: boolean; index: 共线的第一个点索引值 }
  93. public static isCollinear(points: Vec2[] | Vec3[]): { result: boolean; index: number } {
  94. if (points.length < 3) return { result: false, index: -1 };
  95. for (let i = 0; i < points.length - 2; i++) {
  96. const a = points[i];
  97. const b = points[i + 1];
  98. const c = points[i + 2];
  99. if (this.isCollinearThreePoints(a, b, c)) {
  100. return { result: true, index: i };
  101. }
  102. }
  103. return { result: false, index: -1 };
  104. }
  105. //判断三点是否共线
  106. //注意: Vec3的z轴会被忽略
  107. public static isCollinearThreePoints(a: Vec2 | Vec3, b: Vec2 | Vec3, c: Vec2 | Vec3): boolean {
  108. const abx = b.x - a.x;
  109. const aby = b.y - a.y;
  110. const acx = c.x - a.x;
  111. const acy = c.y - a.y;
  112. const cross = abx * acy - aby * acx;
  113. return Math.abs(cross) < 0.1; // 使用叉积判断共线
  114. }
  115. //判断一组Vec2[]或者Vec3[]中是否有重复点
  116. //注意: Vec3[]的z轴会被忽略
  117. public static hasDuplicatePoints(points: Vec2[] | Vec3[]): boolean {
  118. for (let i = 0; i < points.length; i++) {
  119. for (let j = i + 1; j < points.length; j++) {
  120. if (points[i].x === points[j].x && points[i].y === points[j].y) {
  121. return true;
  122. }
  123. }
  124. }
  125. return false;
  126. }
  127. /**
  128. * 生成一个随机五位字符串
  129. * @returns {string}
  130. */
  131. public static randomTmpIDString(): string {
  132. const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  133. let result = '';
  134. for (let i = 0; i < 5; i++) {
  135. result += chars.charAt(Math.floor(Math.random() * chars.length));
  136. }
  137. return result;
  138. }
  139. // 计算当前颜色的HSL值
  140. public static rgbToHsl(r: number, g: number, b: number) {
  141. r /= 255;
  142. g /= 255;
  143. b /= 255;
  144. const max = Math.max(r, g, b),
  145. min = Math.min(r, g, b);
  146. let h = 0,
  147. s = 0,
  148. l = (max + min) / 2;
  149. if (max !== min) {
  150. const d = max - min;
  151. s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
  152. switch (max) {
  153. case r:
  154. h = (g - b) / d + (g < b ? 6 : 0);
  155. break;
  156. case g:
  157. h = (b - r) / d + 2;
  158. break;
  159. case b:
  160. h = (r - g) / d + 4;
  161. break;
  162. }
  163. h /= 6;
  164. }
  165. return [h, s, l];
  166. }
  167. // 将HSL转换为RGB
  168. public static hslToRgb(h: number, s: number, l: number) {
  169. let r, g, b;
  170. if (s === 0) {
  171. r = g = b = l;
  172. } else {
  173. const hue2rgb = (p: number, q: number, t: number) => {
  174. if (t < 0) t += 1;
  175. if (t > 1) t -= 1;
  176. if (t < 1 / 6) return p + (q - p) * 6 * t;
  177. if (t < 1 / 2) return q;
  178. if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
  179. return p;
  180. };
  181. const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
  182. const p = 2 * l - q;
  183. r = hue2rgb(p, q, h + 1 / 3);
  184. g = hue2rgb(p, q, h);
  185. b = hue2rgb(p, q, h - 1 / 3);
  186. }
  187. return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
  188. }
  189. /**
  190. * 比较两个版本号(格式如 "3.8.0")
  191. * @param v1 版本号1
  192. * @param v2 版本号2
  193. * @returns
  194. * - 正数:v1 > v2
  195. * - 负数:v1 < v2
  196. * - 0:v1 == v2
  197. */
  198. public static compareVersions(v1: string, v2: string): number {
  199. // 1. 分割版本号为数字数组
  200. const parts1 = v1.split('.').map(Number);
  201. const parts2 = v2.split('.').map(Number);
  202. // 2. 逐级比较(从左到右)
  203. for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
  204. const num1 = parts1[i] || 0; // 缺失的位补0
  205. const num2 = parts2[i] || 0;
  206. if (num1 !== num2) {
  207. return num1 - num2; // 优先级高的位不等时直接返回结果
  208. }
  209. }
  210. // 3. 所有位均相等
  211. return 0;
  212. }
  213. //检测某层名字是否已设置
  214. public static layerExist(name: string): boolean {
  215. const n = Layers.nameToLayer(name);
  216. if (n > 0) return true;
  217. const lname = Layers.layerToName(0);
  218. if (lname !== name) {
  219. return false;
  220. }
  221. return true;
  222. }
  223. //检查并添加新层,添加后在编辑器设置中可能未刷新.
  224. public static checkAndAddLayerByName(name: string): number {
  225. if (this.layerExist(name)) {
  226. console.log(`${name} 已存在 ,无需获取空闲位置`);
  227. return 1 << Layers.nameToLayer(name);
  228. }
  229. let n = -1;
  230. for (let i = 0; i < 20; i++) {
  231. if (Layers.layerToName(i) == undefined) {
  232. n = i;
  233. break;
  234. }
  235. }
  236. if (n !== -1) {
  237. Layers.addLayer(name, n);
  238. console.warn(`添加新层: ${name},位置: ${n} ,如果在编辑器中,你需要关闭 项目设置 窗口后重新打开才能正常显示`);
  239. return 1 << n;
  240. } else {
  241. console.warn(`无法添加新层: ${name},没有可用的层位置。请检查层设置。`);
  242. return -1;
  243. }
  244. }
  245. }